单例模式确保一个类只有一个实例,并提供一个全局访问点
类图
线程安全问题
有线程安全问题的单例模式:
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
上面代码会有一个问题,当多个线程同时调用 getInstance()
方法时,可能会产生多个instance
实例,因此这种方式并不是真正的单例。
所以我们实现单例模式的核心就在于两点:
- 保证系统中只有一个实例
- 解决获取单例时线程安全
饿汉式
虚拟机加载时直接实例化好:
private static Singleton instance = new Singleton();
懒汉式
需要的时候才创建实例,但需要加synchronized
锁来保证线程安全:
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
这种方式有个问题,每次执行getInstance()
都会加锁,操作比较重,实际上我们只需要实例化的那一刻加锁就可以了
双重校验锁
public class Singleton {
private volatile static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
这种方式有两个关键点:
- instance要使用
volatile
关键字修饰,可以防止JVM指令重排 if (instance == null)
这行有可能多个线程同时执行从而导致被多次实例化,因此synchronized同步块中需要再做一次判断
静态内部类
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这种写法有一个特点,内部静态类SingletonHolder
默认情况不会被JVM加载,只有当getInstance()
被调用时,SingletonHolder
才会被加载。
这种方式的线程安全是由JVM来确保
枚举方式
class Singleton{
}
public enum SingletonEnum {
INSTANCE;
private Singleton instance;
SingletonEnum() {
instance = new Singleton();
}
public Singleton getInstance() {
return instance;
}
}
当我们定义了一个SingletonEnum
类型的枚举INSTANCE
,JVM帮我们创建好了INSTANCE
实例,而我们在构造方法中初始化了Singleton
实例。
这种方式其实是利用了一个枚举值只会被初始化一次的这种特性,从而保证了单例